package universum.engine;

import universum.bi.IBeing;
import universum.util.ioUtility;

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.Map;

class BeingLoader extends URLClassLoader {
    private static BeingLoader instance;
    private Map<String, Class> beingsCache;
    private ioUtility io = ioUtility.getInstance();

    private BeingLoader() {
        super(new URL[0], Universe.class.getClassLoader());
        beingsCache = new HashMap<String, Class>();
    }

    public static BeingLoader getInstance() {
        if (instance == null) instance = new BeingLoader();
        return instance;
    }

    private void addClassPath(String path) {
        try {
            addURL(new URL("file:" + path));
        } catch (MalformedURLException e) {
            io.writelnError(e);
        }
    }

    public IBeing createBeing(final String className, final String filePath) throws ClassNotFoundException {
        return createBeing(getBeingClass(className, filePath));
    }

    public IBeing createBeing(Class beingClass) {return JungleSecurity.invokeInSecureContext(getUnsafeBeingInitializer(beingClass));}
   
    public Class getBeingClass(String className, String filePath) throws ClassNotFoundException {
        Class result;
        if (beingsCache.containsKey(className)) result = beingsCache.get(className);
        else {
            if (filePath != null && !filePath.equals("")) addClassPath(filePath);
            result = loadBeingClass(className);
        }
        return result;
    }

    private boolean classNameIsValid(String className) {
        if (className == null) return false;
        int dot = className.lastIndexOf('.');
        if (dot > 0) {
            JungleSecurity js = (JungleSecurity) System.getSecurityManager();
            if (js != null) js.addAllowed(className.substring(0, dot));
        }
        return true;
    }

    private PrivilegedAction<Class> getUnsafeClassLoader(final String className) {
        return new PrivilegedAction<Class>() {
            public Class run() {
                try {
                    return loadClass(className);
                } catch (Throwable t) {
                    io.writelnError(t);
                    return null;
                }                
            }
        };
    }

    private PrivilegedAction<IBeing> getUnsafeBeingInitializer(final Class beingClass) {
        return new PrivilegedAction<IBeing>() {
            public IBeing run() {
                try {
                    return (IBeing) beingClass.newInstance();
                } catch (Exception e) {
                    io.writelnError(e);
                    return null;
                }
            }
        };
    }

    private Class loadBeingClass
            (String
                    className) throws ClassNotFoundException {
        Class result;
        io.writeDebug("trying to load class " + className + "...");
        if (classNameIsValid(className))
            result = JungleSecurity.invokeInSecureContext(getUnsafeClassLoader(className));
        else throw new IllegalArgumentException("invalid class name specified");
        io.writeInfo("loaded class " + className);
        if (beingClassIsValid(result)) {
            beingsCache.put(className, result);
            return result;
        } else return null;
    }

    private boolean beingClassIsValid
            (Class
                    beingClass) {
        if (beingClass == null) return false;
        try {
            if (!Object.class.getMethod("hashCode").equals(beingClass.getMethod("hashCode"))) return false;
            if (!Object.class.getMethod("equals" , Object.class).equals(beingClass.getMethod("equals" , Object.class)))
                return false;
            if (!Object.class.getMethod("getClass").equals(beingClass.getMethod("getClass"))) return false;
        }
        catch (Throwable e) {
            io.writelnError(e);
            return false;
        }
        return true;
    }


}
